import json
from datetime import datetime
from enum import Enum
from typing import Generic, TypeVar, Dict, Optional, Union, Any, List

from pydantic import BaseModel, validator
from pydantic.generics import GenericModel

from apps.crawler_platform.core_platform.core_g import CoreSqlValue

DataT = TypeVar('DataT')
DataX = TypeVar('DataX')


class InputPlatformModel(GenericModel, Generic[DataT]):
    input_code: str = ''
    input_msg: str = ''
    data: DataT


class journalHomeMode(BaseModel):
    task_name: str = ''
    task_tag: str = ''
    home_rawid: str = ''
    page_index:int = 0


class journalInputMode(BaseModel):
    task_name: str = ''
    task_tag: str = ''
    journal_rawid: str = ''


class journalIssueMode(BaseModel):
    task_name: str = ''
    task_tag: str = ''
    journal_rawid: str = ''
    pub_year: str = ''
    num: str = ''


class journalArticleMode(BaseModel):
    task_name: str = ''
    task_tag: str = ''
    rawid: str = ''


class JournalIssueInputMode(BaseModel):
    task_name: str = ''
    task_tag: str = ''
    journal_rawid: str = ''


class HtmlModel(BaseModel):
    html: Optional[str] = None
    state_dict: Any = ""
    down_date: str = str(datetime.now())


class PageHtmlModel(GenericModel, Generic[DataT]):
    """
    不要给默认值,否者使用Union会出现匹配不到model的情况
    """
    page_html: Dict[str, DataT]


class TypeHtmlModel(GenericModel, Generic[DataX]):
    """
    不要给默认值,否者使用Union会出现匹配不到model的情况
    """
    type_code: Dict[str, DataX]


class MultiplePageHtmlModel(GenericModel, Generic[DataT, DataX]):
    """
    不要给默认值,否者使用Union会出现匹配不到model的情况
    """
    first_page: DataT
    type_page: TypeHtmlModel[DataX]


T1 = Union[PageHtmlModel[HtmlModel], HtmlModel]
T2 = Union[MultiplePageHtmlModel[HtmlModel, T1], HtmlModel]


# T3 = Union[TypeHtmlModel[PageHtmlModel[HtmlModel]], HtmlModel]


# 下载的model
class DownModel(GenericModel, Generic[DataT]):
    down_dict: Dict[str, DataT]

    def set_parse_obj(self, obj):
        """
        解决载入数据是类方法的问题
        :param obj:
        :return:
        """
        return self.__class__.parse_obj(obj)


class ParaModel(BaseModel):
    # 解析规则字典
    parse_dict: dict = {}
    # 下载的html字典
    html_model: HtmlModel
    call_back: str = ""


class ParaAllModel(BaseModel):
    parse_dict: Dict[str, ParaModel] = {}


class DealInsertModel(BaseModel):
    lists: List[dict] = []
    insert_pre: str = ""


class DescriptionEnum(str, Enum):
    and_d = 'and'
    or_d = 'or'


class OperatorSqlModel(BaseModel):
    """
    sql的项由以下三部分组成
    """
    key: str = ""
    value: str = ""
    operator: str = ""
    description: DescriptionEnum = DescriptionEnum.and_d


class DealUpdateModel(BaseModel):
    # 兼容sql语句 +1 这样的操作（"update_no_placeholder":{"a":"a+1"}）
    update_no_placeholder: dict = {}
    # 正常更新的字典
    update: dict = {}
    # 这里做一个兼容，允许简单的字典，表示默认的等于和and组合 也可以用后面的List[OperatorSqlModel]构建复杂的sql
    # 这里原来使用dict，不想改太多东西 做的兼容
    where: Union[dict, List[OperatorSqlModel]] = {}


class DealItemModel(BaseModel):
    # 兼容sql语句 +1 这样的操作（"update_no_placeholder":{"a":"a+1"}）
    update_no_placeholder: dict = {}
    # 正常更新的字典
    update: dict = {}
    # 条件更新能力
    update_list: List[DealUpdateModel] = []
    #  插入的语句,可能按照多个模式插入 比如insert 和 repalce
    insert: List[DealInsertModel] = []


class DealItemNextModel(BaseModel):
    update_list: List[DealUpdateModel] = []
    #  插入的语句,可能按照多个模式插入 比如insert 和 repalce
    insert: List[DealInsertModel] = []


class DealModel(BaseModel):
    # 写入上一级数据库的字典
    # 一个特殊的键 兼容sql语句 +1 这样的操作（"update_no_placeholder":{"a":"a+1"}）
    befor_dicts: DealItemModel = DealItemModel()
    # 写入下一级数据库的字典, 可在callback覆盖
    # next_dicts: dict = {"lists": [], "insert": CoreSqlValue.insert_ig_it}
    next_dicts: DealItemNextModel = DealItemNextModel()
    # 多余的字典，可能是由于本次数据没有收录
    other_dicts: dict = {}
    # 没有数据的字典，网页上本条数据没有这个字段
    null_dicts: dict = {}
    # 特殊的字段，程序内部使用，不会写入数据库
    code_dicts: dict = {
        # 翻页时会启用该字段，告诉我解析出来的最大页
        "1_1": {"max_page": ""}  # 多个翻页时建议使用key区分
    }


# 数据库 taskinfo 对应的model，主要记录下载需要的信息
class TaskInfoModel(BaseModel):
    # 任务名，一般一个网站一个任务名
    task_name: str = ""
    # 任务标签，一般一个步骤一个任务标签，比如期刊分为cnkijournallist，cnkiissue，cnkiarticle
    # 当然也兼容一些特殊的情况，比如海外版,网络首发等
    task_tag: str = ""
    # 这一步后下一步的tag，可以是多个;号分割
    task_tag_next: str = ""
    # 分组，加入分组是为了防止同一tag下的多路分支请求且相互不依赖的问题
    # 比如 期刊的海外版和国内版
    groups: str = ""
    # 同一组下的数据需要多个请求获取，且有可能需要先后顺序的依赖
    order_num: int = 1
    # url 请求的url地址
    url: str = ""
    # 请求的类型
    req_type: str = ""
    # 自己封装的一些中间件，进行验证
    resp_middler: str = ""
    # 请求头
    headers: str = ""
    # get的 请求参数
    params: str = ""
    # post的data参数
    post_data: str = ""
    # html中需要存在的字符串
    marks: str = ""
    # 解析规则
    parse_rules: str = ""
    # 验证规则
    validate_rules: str = ""
    # 请求超时
    timeout: int = ""
    # 自己写的方法 需要加到resp_middler中去的
    funcs: str = ""
    # 0: 代表没有翻页 1: 代表从0开始翻页 2: 代表从1开始翻页
    turn_page: int = 0
    # 任务打标键值对{"stat":"failcount"}
    state_key: str = ""
    # post 是不是json格式传输 1为是 0为不是
    post_data_is_json: str = ""
    # redis的更新时间
    redis_uptime: Optional[str] = ""


class TaskInfoSaveSettingChangeModel(BaseModel):
    # 任务名，一般一个网站一个任务名
    task_name: str = ""
    # 任务标签，一般一个步骤一个任务标签，比如期刊分为cnkijournallist，cnkiissue，cnkiarticle
    # 当然也兼容一些特殊的情况，比如海外版,网络首发等
    task_tag: str = ""
    # 用作表示子任务所属表
    database_table_name: str = "taskinfo_save_setting"
    # taskinfo_save_setting表中的table_name字段
    table_name: str = ""
    # 用于确定本次请求归属, 0: 表示本次请求输入的参数 为关闭参数(即将输入tag_state或task_state置为0,其余置为1)
    # 1: 表示本次请求输入的参数 为开启参数(即将输入tag_state或task_state置为1, 其余置为0)
    switch: str = "1"
    # 用于控制本次请求是否为整个任务控制, 0: 表示本次请求不是以task_state 为整体(即将task_state置为1, 其余不做操作);
    # 1: 表示本次请求是控制整个任务是以task_state 为整体(即将task_state置为1, 其余置为0)
    is_task: str = "0"


# 保存nosql需要的配置
class TaskInfoSaveSettingModel(BaseModel):
    # 任务名，一般一个网站一个任务名
    task_name: str = ""
    # 任务标签，一般一个步骤一个任务标签，比如期刊分为cnkijournallist，cnkiissue，cnkiarticle
    # 当然也兼容一些特殊的情况，比如海外版,网络首发等
    task_tag: str = ""
    # 需要哪些sql里的键组成 nosql的主键
    id_list: str = ""
    # nosql 的表名，在后台表明统一管理，不用担心跨库问题
    nosql_table: str = ""
    # 保存历史记录个数
    history_cnt: int = 1
    # redis的更新时间
    redis_uptime: Optional[str] = ""
    # 用作表示任务总体下载控制
    task_state: int = 0
    # 用作表示子任务总体下载控制
    tag_state: int = 0
    # 用作表示子任务所属表
    table_name: str = ""
    # 用作表示子任务对象字段
    fianl_stat: str = ""
    # 用作表示单个任务代理单位时间请求次数
    proxy_ctrl: int = 0

    def get_id_list(self):
        return eval(self.id_list)


class TaskAllModel(BaseModel):
    """
    将两个redis集成到一个model
    """
    task_set: TaskInfoSaveSettingModel
    task_info: TaskInfoModel


class RedisAllTaskModel(BaseModel):
    parse_dict: Dict[str, TaskAllModel] = {}


# 期刊列表model
class JournalListModel(BaseModel):
    # 自增主id
    id: int
    # 任务名，一般一个网站一个任务名
    task_name: str = ""
    # 任务标签，一般一个步骤一个任务标签，比如期刊分为cnkijournallist，cnkiissue，cnkiarticle
    # 当然也兼容一些特殊的情况，比如海外版,网络首发等
    task_tag: str = ""
    # 某个网站期刊的主键
    journal_rawid: str = ""
    # 维普定义的某个库的id
    sub_db_id: str = ""
    # json格式的str,期刊的其他信息
    journal_json: str = ""
    # 是否存活
    is_active: int = 1
    # 状态
    state: int = 0
    failcount: int = 0
    online_stat: int = 0
    online_failcount: int = 0
    create_time: datetime
    update_time: datetime
    other_dicts: str = ""
    null_dicts: str = ""
    err_msg: str = ""

    @validator('null_dicts', 'other_dicts', 'err_msg', pre=True)
    def split_str(cls, v):
        if v is None:
            return ""
        return v

    def get_dict(self):
        """
        将json数据扩展到外层使用
        :return:
        """
        dicts = self.dict()
        journal_json = json.loads(dicts["journal_json"])
        dicts.update(journal_json)
        return dicts


# 期列表model
class JournalIssueModel(BaseModel):
    # 自增主id
    id: int
    # 任务名，一般一个网站一个任务名
    task_name: str = ""
    # 任务标签，一般一个步骤一个任务标签，比如期刊分为cnkijournallist，cnkiissue，cnkiarticle
    # 当然也兼容一些特殊的情况，比如海外版,网络首发等
    task_tag: str = ""
    # 某个网站期刊的主键
    journal_rawid: str = ""
    # 维普定义的某个库的id
    sub_db_id: str = ""
    # 出版年
    pub_year: str = ""
    # 期
    num: str = ""
    # json格式的str,期的其他信息, 目前仅超星使用 ZCN
    issue_json: str = ""
    # 页
    page: str = ""
    # 是否存活
    is_active: int = 1
    # 状态
    state: int = 0
    failcount: int = 0
    create_time: datetime
    update_time: datetime
    other_dicts: str = ""
    null_dicts: str = ""
    err_msg: str = ""

    @validator('null_dicts', 'other_dicts', 'err_msg', 'page', pre=True)
    def split_str(cls, v):
        if v is None:
            return ""
        return v

    def get_dict(self):
        """
        将json数据扩展到外层使用
        :return:
        """
        dicts = self.dict()
        journal_json = json.loads(dicts["issue_json"])
        dicts.update(journal_json)
        return dicts


class CallBackModel(GenericModel, Generic[DataT]):
    # 通过xpath或者css 、json解析出来的字典
    para_dicts: dict = {}
    # sql查询出来的Model
    sql_model: DataT
    # redis所有配置信息
    redis_all: RedisAllTaskModel


# 期列表model
class JournalArticleModel(BaseModel):
    # 自增主id
    id: int
    task_name: str = ""
    task_tag: str = ""
    # 某个网站期刊的主键
    rawid: str = ""
    # 维普定义的某个库的id
    sub_db_id: str = ""
    article_info_json: str = ""
    state: int = ""
    failcount: int = ""
    ref_stat: int = ""
    ref_failcount: int = 0
    # 状态
    oversea: int = 0
    oversea_failcount: int = 0
    re_down: int = ""
    is_true: int = ""
    is_true_failcount: int = 0
    create_time: datetime
    update_time: datetime
    other_dicts: str = ""
    null_dicts: str = ""
    err_msg: str = ""
    page: str = "0"

    @validator('null_dicts', 'other_dicts', 'err_msg', pre=True)
    def split_str(cls, v):
        if v is None:
            return ""
        return v

    def set_json(self, up_dicts):
        dicts = self.dict()
        journal_json = json.loads(dicts["article_info_json"])
        journal_json.update(up_dicts)
        self.article_info_json = json.dumps(journal_json, ensure_ascii=False)

    def get_dict(self):
        """
        将json数据扩展到外层使用
        :return:
        """
        dicts = self.dict()
        journal_json = json.loads(dicts["article_info_json"])
        dicts.update(journal_json)
        return dicts


# 期刊首页model
class JournalHomeModel(BaseModel):
    # 自增主id
    id: int
    # 任务名，一般一个网站一个任务名
    task_name: str = ""
    # 任务标签，一般一个步骤一个任务标签，比如期刊分为cnkijournallist，cnkiissue，cnkiarticle
    # 当然也兼容一些特殊的情况，比如海外版,网络首发等
    task_tag: str = ""
    # 某个网站的主键
    home_rawid: str = ""
    # 维普定义的某个库的id
    sub_db_id: str = ""
    page_index: str = ""

    # json格式的str,
    home_json: str = ""
    # 页
    page: str = ""
    # 状态
    state: int = 0
    failcount: int = 0
    create_time: datetime
    update_time: datetime
    other_dicts: str = ""
    null_dicts: str = ""
    err_msg: str = ""

    @validator('null_dicts', 'other_dicts', 'err_msg', 'page', pre=True)
    def split_str(cls, v):
        if v is None:
            return ""
        return v

    def get_dict(self):
        """
        将json数据扩展到外层使用
        :return:
        """
        dicts = self.dict()
        home_json = json.loads(dicts["home_json"])
        dicts.update(home_json)
        return dicts


# ############################## 以下  paper

class PaperDaysMode(BaseModel):
    task_name: str = ""
    task_tag: str = ""
    year: str = ""
    months: str = ""
    days: str = ""


class PaperDaysModel(BaseModel):
    # 自增主id
    id: int
    # 任务名，一般一个网站一个任务名
    task_name: str = ""
    task_tag: str = ""
    year: str = ""
    months: str = ""
    days: str = ""
    url: str = ""
    # json格式的str,
    days_json: str = ""
    # 状态
    state: int = 0
    failcount: int = 0
    count: int = 0
    create_time: datetime
    update_time: datetime
    other_dicts: str = ""
    null_dicts: str = ""
    err_msg: str = ""

    def get_dict(self):
        """
        将json数据扩展到外层使用
        :return:
        """
        dicts = self.dict()
        days_json = json.loads(dicts["days_json"])
        dicts.update(days_json)
        return dicts


class PaperPageMode(BaseModel):
    task_name: str = ""
    task_tag: str = ""
    rawid_page: str = ""


class PaperPageModel(BaseModel):
    # 自增主id
    id: int
    # 任务名，一般一个网站一个任务名
    task_name: str = ""
    task_tag: str = ""
    rawid_page: str = ""

    # 状态
    state: int = 0
    failcount: int = 0
    count: int = 0
    create_time: datetime
    update_time: datetime
    # json格式的str,
    page_json: str = ""
    other_dicts: Optional[str] = None
    null_dicts: Optional[str] = None
    err_msg: Optional[str] = None

    def get_dict(self):
        """
        将json数据扩展到外层使用
        :return:
        """
        dicts = self.dict()
        days_json = json.loads(dicts["page_json"])
        dicts.update(days_json)
        return dicts


class PaperArticleMode(BaseModel):
    task_name: str = ""
    task_tag: str = ""
    rawid: str = ""


class PaperArticleModel(BaseModel):
    # 自增主id
    id: int
    # 任务名，一般一个网站一个任务名
    task_name: str = ""
    task_tag: str = ""
    rawid: str = ""

    # 状态
    state: int = 0
    failcount: int = 0
    count: int = 0
    create_time: datetime
    update_time: datetime
    # json格式的str,
    article_json: str = ""
    other_dicts: Optional[str] = None
    null_dicts: Optional[str] = None
    err_msg: Optional[str] = None

    def get_dict(self):
        """
        将json数据扩展到外层使用
        :return:
        """
        dicts = self.dict()
        days_json = json.loads(dicts["article_json"])
        dicts.update(days_json)
        return dicts